/**
 * Autotab - jQuery plugin 1.8.1
 * https://github.com/Mathachew/jquery-autotab
 * 
 * Copyright (c) 2008, 2014 Matthew Miller
 * 
 * Licensed under the MIT licensing:
 *   http://www.opensource.org/licenses/mit-license.php
 */

(function ($) {
    var platform = navigator.platform,
        settings = {
            tabPause: 800,
            focusChange: null,
            iOS: (platform === 'iPad' || platform === 'iPhone' || platform === 'iPod'),
            firefox: (typeof InstallTrigger !== 'undefined')
        };

    var setSettings = function (e, settings) {
        if (settings === null || typeof settings === 'undefined') {
            return;
        }

        for (var key in settings) {
            $(e).data('autotab-' + key, settings[key]);
        }
    };

    var getSettings = function (e) {
        var settings = {
            format: 'all',
            loaded: false,
            disabled: false,
            pattern: null,
            uppercase: false,
            lowercase: false,
            nospace: false,
            maxlength: 2147483647,
            target: null,
            previous: null,
            trigger: null,
            originalValue: '',
            changed: false,
            editable: (e.type == 'text' || e.type == 'password' || e.type == 'textarea'),
            tabOnSelect: false
        };

        // If $.autotab.selectFilterByClas is true and the format not specified, automatically select an element's format based on a matching class name.
        // The first matched element becomes the selected format for the filter.
        if ($.autotab.selectFilterByClass === true && typeof $(e).data('autotab-format') === 'undefined') {
            var classes = ['all', 'text', 'alpha', 'number', 'numeric', 'alphanumeric', 'hex', 'hexadecimal', 'custom'];

            for (var key in classes) {
                if ($(e).hasClass(classes[key])) {
                    settings.format = classes[key];
                    break;
                }
            }
        }

        for (var key in settings) {
            if (typeof $(e).data('autotab-' + key) !== 'undefined') {
                settings[key] = $(e).data('autotab-' + key);
            }
        }

        // Save settings on first run
        if (!settings.loaded) {
            if (settings.trigger !== null && typeof settings.trigger === 'string') {
                settings.trigger = settings.trigger.toString();
            }

            setSettings(e, settings);
        }

        return settings;
    };

    var queryObject = function (e) {
        return (typeof e !== 'undefined' && (typeof e === 'string' || !(e instanceof jQuery)));
    };

    $.autotab = function (options) {
        if (typeof options !== 'object') {
            options = {};
        }

        $(':input').autotab(options);
    };

    $.autotab.selectFilterByClass = false;

    $.autotab.next = function () {
        var e = $(document.activeElement);

        if (e.length) {
            e.trigger('autotab-next');
        }
    };

    $.autotab.previous = function () {
        var e = $(document.activeElement);

        if (e.length) {
            e.trigger('autotab-previous');
        }
    };

    $.autotab.remove = function (e) {
        queryObject(e) ? $(e).autotab('remove') : $(':input').autotab('remove');
    };

    $.autotab.restore = function (e) {
        queryObject(e) ? $(e).autotab('restore') : $(':input').autotab('restore');
    };

    $.autotab.refresh = function (e) {
        queryObject(e) ? $(e).autotab('refresh') : $(':input').autotab('refresh');
    };

    $.fn.autotab = function (method, options) {
        if (!this.length) {
            return this;
        }

        // Remove hidden fields since tabbing backwards is supported on different form elements
        var filtered = $.grep(this, function (e, i) {
            return e.type != 'hidden';
        });

        // Apply filter options
        if (method == 'filter') {
            if (typeof options === 'string' || typeof options === 'function') {
                options = { format: options };
            }

            for (var i = 0, length = filtered.length; i < length; i++) {
                var defaults = getSettings(filtered[i]),
                    newOptions = options;

                // Retain the established target/previous values as this area is for filtering only
                newOptions.target = defaults.target;
                newOptions.previous = defaults.previous;

                $.extend(defaults, newOptions);

                if (!defaults.loaded) {
                    defaults.disabled = true;
                    autotabBind(filtered[i], newOptions);
                }
                else {
                    setSettings(filtered[i], defaults);
                }
            }
        }
        // Disable auto tab and filtering
        else if (method == 'remove' || method == 'destroy' || method == 'disable') {
            for (var i = 0, length = filtered.length; i < length; i++) {
                var defaults = getSettings(filtered[i]);

                defaults.disabled = true;

                setSettings(filtered[i], defaults);
            }
        }
        // Re-enable auto tab and filtering
        else if (method == 'restore' || method == 'enable') {
            for (var i = 0, length = filtered.length; i < length; i++) {
                var defaults = getSettings(filtered[i]);

                defaults.disabled = false;

                setSettings(filtered[i], defaults);
            }
        }
        // Refresh target/previous elements
        else if (method == 'refresh') {
            for (var i = 0, length = filtered.length; i < length; i++) {
                var defaults = getSettings(filtered[i]),
                    n = i + 1,
                    p = i - 1,
                    selectTarget = function () {
                        if (i > 0 && n < length) {
                            defaults.target = filtered[n];
                        }
                        else if (i > 0) {
                            defaults.target = null;
                        }
                        else {
                            defaults.target = filtered[n];
                        }   
                    },
                    selectPrevious = function () {
                        if (i > 0 && n < length) {
                            defaults.previous = filtered[p];
                        }
                        else if (i > 0) {
                            defaults.previous = filtered[p];
                        }
                        else {
                            defaults.previous = null;
                        }
                    };

                // Nothing was specified for the target element, so automatically set it
                if (defaults.target === null || defaults.target.selector === '') {
                    selectTarget();
                }
                else if (typeof defaults.target === 'string' || defaults.target.selector) {
                    defaults.target = $(typeof defaults.target === 'string' ? defaults.target : defaults.target.selector);

                    if (defaults.target.length === 0) {
                        selectTarget();
                    }
                }

                // Nothing was specified for the previous element, so automatically set it
                if (defaults.previous === null || defaults.previous.selector === '') {
                    selectPrevious();
                }
                else if (typeof defaults.previous === 'string' || defaults.previous.selector) {
                    defaults.previous = $(typeof defaults.previous === 'string' ? defaults.previous : defaults.previous.selector);

                    if (defaults.previous.length === 0) {
                        selectPrevious();
                    }
                }

                if (!defaults.loaded) {
                    autotabBind(filtered[i], defaults);
                }
                else {
                    if (queryObject(defaults.target)) {
                        defaults.target = $(defaults.target);
                    }

                    if (queryObject(defaults.previous)) {
                        defaults.previous = $(defaults.previous);
                    }

                    setSettings(filtered[i], defaults);
                }
            }
        }
        else {
            if (method === null || typeof method === 'undefined') {
                options = {};
            }
            else if (typeof method === 'string' || typeof method === 'function') {
                options = { format: method };
            }
            else if (typeof method === 'object') {
                options = method;
            }

            // Bind key events to element(s) passed
            if (filtered.length > 1) {
                for (var i = 0, length = filtered.length; i < length; i++) {
                    var n = i + 1,
                        p = i - 1,
                        newOptions = options;

                    if (i > 0 && n < length) {
                        newOptions.target = filtered[n];
                        newOptions.previous = filtered[p];
                    }
                    else if (i > 0) {
                        newOptions.target = null;
                        newOptions.previous = filtered[p];
                    }
                    else {
                        newOptions.target = filtered[n];
                        newOptions.previous = null;
                    }

                    autotabBind(filtered[i], newOptions);
                }
            }
            else {
                autotabBind(filtered[0], options);
            }
        }

        return this;
    };

    var filterValue = function (e, value, defaults) {
        if (typeof defaults.format === 'function') {
            return defaults.format(value, e);
        }

        var pattern = null;

        switch (defaults.format) {
            case 'text':
                pattern = new RegExp('[0-9]+', 'g');
                break;

            case 'alpha':
                pattern = new RegExp('[^a-zA-Z]+', 'g');
                break;

            case 'number':
            case 'numeric':
                pattern = new RegExp('[^0-9]+', 'g');
                break;

            case 'alphanumeric':
                pattern = new RegExp('[^0-9a-zA-Z]+', 'g');
                break;

            case 'hex':
            case 'hexadecimal':
                pattern = new RegExp('[^0-9A-Fa-f]+', 'g');
                break;

            case 'custom':
                pattern = new RegExp(defaults.pattern, 'g');
                break;

            case 'all':
            default:
                break;
        }

        if (pattern !== null) {
            value = value.replace(pattern, '');
        }

        if (defaults.nospace) {
            pattern = new RegExp('[ ]+', 'g');
            value = value.replace(pattern, '');
        }

        if (defaults.uppercase) {
            value = value.toUpperCase();
        }

        if (defaults.lowercase) {
            value = value.toLowerCase();
        }

        return value;
    };

    var autotabBind = function (element, options) {
        var defaults = getSettings(element);

        if (defaults.disabled) {
            defaults.disabled = false;
            defaults.target = null;
            defaults.previous = null;
        }

        $.extend(defaults, options);

        // Sets targets to element based on the name or ID passed if they are not currently objects
        if (queryObject(defaults.target)) {
            defaults.target = $(defaults.target);
        }

        if (queryObject(defaults.previous)) {
            defaults.previous = $(defaults.previous);
        }

        var oldMaxlength = element.maxLength;

        if (typeof element.maxLength === 'undefined' && element.type == 'textarea') {
            oldMaxlength = element.maxLength = element.getAttribute('maxlength');
        }

        // defaults.maxlength has not changed and maxlength was specified
        if (defaults.maxlength == 2147483647 && oldMaxlength != 2147483647 && oldMaxlength != -1) {
            defaults.maxlength = oldMaxlength;
        }
        // defaults.maxlength overrides maxlength
        else if (defaults.maxlength > 0) {
            element.maxLength = defaults.maxlength;
        }
        // defaults.maxlength and maxlength have not been specified
        // A target cannot be used since there is no defined maxlength
        else {
            defaults.target = null;
        }

        if (!defaults.loaded) {
            defaults.loaded = true;
            setSettings(element, defaults);
        }
        else {
            setSettings(element, defaults);
            return;
        }

        // Add a change event to select lists only so that we can auto tab when a value is selected
        if (element.type == 'select-one') {
            $(element).on('change', function (e) {
                var defaults = getSettings(this);

                if (defaults.tabOnSelect) {
                    $(this).trigger('autotab-next');
                }
            });
        }

        // The 1ms timeouts allow for keypress events to complete in case a
        // custom function or exterior method calls for a manual auto tab
        $(element).on('autotab-next', function (event, defaults) {
            var self = this;
            setTimeout(function () {
                if (!defaults) {
                    defaults = getSettings(self);
                }

                var target = defaults.target;

                if (!defaults.disabled && target.length) {
                    // Using focus on iOS devices is a pain, so use the browser's next/previous buttons to proceed
                    if (!settings.iOS) {

                        // Field is disabled/readonly, so tab to next element
                        if (target.prop('disabled') || target.prop('readonly')) {
                            target.trigger('autotab-next');
                        }
                        else {
                            target.focus().select();
                        }

                        settings.focusChange = new Date();
                    }
                }
            }, 1);
        }).on('autotab-previous', function (event, defaults) {
            var self = this;
            setTimeout(function () {
                if (!defaults) {
                    defaults = getSettings(self);
                }

                var previous = defaults.previous;

                if (!defaults.disabled && previous.length) {
                    var value = previous.val();

                    // Field is disabled/readonly, so tab to previous element
                    if (previous.prop('disabled') || previous.prop('readonly')) {
                        previous.trigger('autotab-previous');
                    }
                    else if (value.length && previous.data('autotab-editable')) {
                        previous.focus().val(value.substring(0, value.length - 1));
                        setSettings(previous, { changed: true });
                    }
                    else {
                        previous.focus();
                    }

                    settings.focusChange = null;
                }
            }, 1);
        }).on('focus', function () {
            setSettings(this, { originalValue: this.value });
        }).on('blur', function () {
            var defaults = getSettings(this);

            if (defaults.changed && this.value != defaults.originalValue) {
                setSettings(this, { changed: false });
                $(this).change();
            }
        }).on('keydown', function (e) {
            var defaults = getSettings(this);

            if (!defaults || defaults.disabled) {
                return true;
            }

            var keyCode = e.which || e.charCode;

            // Go to the previous element when backspace
            // is pressed in an empty input field
            if (keyCode == 8) {
                // Prevent the browser from of navigating to the previous page
                if (this.type === 'select-one' || this.type === 'checkbox' || this.type === 'radio' || this.type === 'button' || this.type === 'submit' || this.type === 'range') {
                    $(this).trigger('autotab-previous', defaults);
                    return false;
                }

                if (this.value.length === 0) {
                    $(this).trigger('autotab-previous', defaults);
                    return;
                }

                setSettings(this, { changed: (this.value !== defaults.originalValue) });
            }
            else if (keyCode == 9 && settings.focusChange !== null) {
                // Tab backwards
                if (e.shiftKey) {
                    settings.focusChange = null;
                    return;
                }

                if ((new Date().getTime() - settings.focusChange.getTime()) < settings.tabPause) {
                    settings.focusChange = null;
                    return false;
                }
            }
        }).on('keypress', function (e) {
            var defaults = getSettings(this),
                keyCode = e.which || e.keyCode;

            // e.charCode == 0 indicates a special key has been pressed, which only Firefox triggers
            if (!defaults || defaults.disabled || (settings.firefox && e.charCode === 0) || e.ctrlKey || e.altKey || keyCode == 13 || (this.type != 'text' && this.type != 'password' && this.type != 'textarea') || this.disabled) {
                return true;
            }

            var keyChar = String.fromCharCode(keyCode);

            // Prevents auto tabbing when defaults.trigger is pressed
            if (defaults.trigger !== null && defaults.trigger.indexOf(keyChar) >= 0) {
                if (settings.focusChange !== null && (new Date().getTime() - settings.focusChange.getTime()) < settings.tabPause) {
                    settings.focusChange = null;
                }
                else {
                    $(this).trigger('autotab-next', defaults);
                }

                return false;
            }

            settings.focusChange = null;

            var hasValue = document.selection && document.selection.createRange ? true : (e.charCode > 0);

            keyChar = filterValue(this, keyChar, defaults);

            if (hasValue && (keyChar === null || keyChar === '')) {
                return false;
            }

            // Many, many thanks to Tim Down for this solution: http://stackoverflow.com/a/3923320/94656
            if (hasValue && (this.value.length <= this.maxLength)) {
                var start, end,
                    selectionType = 0;

                if (typeof this.selectionStart === 'number' && typeof this.selectionEnd === 'number') {
                    // Non-IE browsers and IE 9
                    start = this.selectionStart;
                    end = this.selectionEnd;
                    selectionType = 1;
                }
                else if (document.selection && document.selection.createRange) {
                    // For IE up to version 8
                    var selectionRange = document.selection.createRange(),
                        textInputRange = this.createTextRange(),
                        precedingRange = this.createTextRange(),
                        bookmark = selectionRange.getBookmark();
                    textInputRange.moveToBookmark(bookmark);
                    precedingRange.setEndPoint("EndToStart", textInputRange);
                    start = precedingRange.text.length;
                    end = start + selectionRange.text.length;
                    selectionType = 2;
                }

                // Text is fully selected, so it needs to be replaced
                if (start === 0 && end == this.value.length) {
                    this.value = keyChar;
                    setSettings(this, { changed: (this.value != defaults.originalValue) });
                }
                else {
                    if (this.value.length == this.maxLength && start === end) {
                        $(this).trigger('autotab-next', defaults);
                        return false;
                    }

                    this.value = this.value.slice(0, start) + keyChar + this.value.slice(end);
                    setSettings(this, { changed: (this.value != defaults.originalValue) });
                }

                // Prevents the cursor position from being set to the end of the text box
                // This is called even if the text is fully selected and replaced due to an unexpected behavior in IE6 and up (#32)
                if (this.value.length != defaults.maxlength) {
                    start++;

                    if (selectionType == 1) {
                        this.selectionStart = this.selectionEnd = start;
                    }
                    else if (selectionType == 2) {
                        var range = this.createTextRange();
                        range.collapse(true);
                        range.moveEnd('character', start);
                        range.moveStart('character', start);
                        range.select();
                    }
                }
            }

            if (this.value.length == defaults.maxlength) {
                $(this).trigger('autotab-next', defaults);
            }

            return false;
        }).on('paste', function (e) {
            var defaults = getSettings(this);

            if (!defaults) {
                return true;
            }

            this.maxLength = 2147483647;

            (function (e, originDefaults) {
                setTimeout(function () {
                    var lastIndex = -1,
                        hiddenInput = document.createElement('input');
                    hiddenInput.type = 'hidden';
                    hiddenInput.value = e.value.toLowerCase();
                    hiddenInput.originalValue = e.value;

                    e.maxLength = originDefaults.maxlength;
                    e.value = filterValue(e, e.value, originDefaults).substr(0, originDefaults.maxlength);

                    var handlePaste = function (e, previousValue) {
                        if (!e) {
                            return;
                        }

                        var defaults = getSettings(e);

                        if ($(e).prop('disabled') || $(e).prop('readonly')) {
                            $(e).trigger('autotab-next');

                            if (!settings.iOS) {
                                handlePaste(defaults.target[0], previousValue);
                            }
                            return;
                        }

                        for (var i = 0, count = previousValue.length; i < count; i++) {
                            lastIndex = hiddenInput.value.indexOf(previousValue.charAt(i).toLowerCase(), lastIndex) + 1;
                        }

                        var trimmedValue = hiddenInput.originalValue.substr(lastIndex),
                            filteredValue = filterValue(e, trimmedValue, defaults).substr(0, defaults.maxlength);

                        if (!filteredValue) {
                            e.value = '';
                            return;
                        }

                        e.value = filteredValue;

                        if (filteredValue.length == defaults.maxlength) {
                            $(e).trigger('autotab-next', defaults);

                            if (!settings.iOS) {
                                handlePaste(defaults.target[0], filteredValue);
                            }
                        }

                    };

                    if (e.value.length == originDefaults.maxlength) {
                        $(e).trigger('autotab-next', defaults);

                        if (!settings.iOS) {
                            handlePaste(originDefaults.target[0], e.value.toLowerCase());
                        }
                    }
                }, 1);
            })(this, defaults);
        });
    };

    // Deprecated, here for backwards compatibility
    $.fn.autotab_magic = function (focus) {
        return $(this).autotab();
    };
    $.fn.autotab_filter = function (options) {
        var defaults = {};

        if (typeof options === 'string' || typeof options === 'function') {
            defaults.format = options;
        }
        else {
            $.extend(defaults, options);
        }

        return $(this).autotab('filter', defaults);
    };

})(jQuery);